package org.reborn.engine.system

import SystemMessage._
import org.reborn.utils.Logging
import org.apache.log4j.Logger
import scala.actors.Actor
import scala.actors.Actor._

/**
 * This class defines a game engine "system" such as the graphics, input, physics, sound, or any other
 * set of contained logic that requires some amount of logic to update its state. A System might require 
 * access to other Systems, and other Systems might in turn be dependent on this System.
 * 
 * The main purpose of creating a System is to allow the game engine to decide when a particular update
 * takes place, and to make sure that all dependencies are satisfied based on their dependency hierarchy.
 * 
 * For instance, the Sound system and the Graphics system might both depend on the Logic system, but once
 * the Logic system has updated, they can both execute independently and potentially in parallel.
 */
abstract class System(dependsOn:System*) extends Actor with Logging{
	
	private var _requiredSystems = Set[System]()
	private var _dependentSystems = Set[System]()
	
	private var _notCompletedSystems = Set[System]()
	private var _notSetupSystems = Set[System]()
	private var _notShutdownSystems = Set[System]()
	
	private var _completed = false;
	private var _setup = false;
	private var _shutdown = false;
	
	for(sys <- dependsOn){
		addRequiredSystem(sys)
	}
	
	def this(){
		this(Array[System]():_*)
	}
	
	
	
	final protected[system] def startSystem(){
		log.debug("System started.")
		_notSetupSystems = Set(_requiredSystems.toArray:_*)
		_notShutdownSystems = Set(_dependentSystems.toArray:_*)
		this.start()
	}

	//Set up an actor that various lifecycle messages:
	//UpdateComplete tells this actor that one its required
	//dependencies has completed its update. If all required dependencies
	//have completed, then it invokes its own update logic and notifies
	//all of its downstream dependencies
	//The Reset message tells this System that it's time for another update loop
	//and that all of its required dependencies now need to be run again before
	//it can invoke its update logic. This message will be sent to all Systems 
	//at the beginning of each loop.
	final def act(){
		loop{
			react{
				case SetupComplete(sys) => {
					if(sys != null)
						_notSetupSystems -= sys
					
					if(_notSetupSystems.isEmpty){
						setup()
						_setup = true;
						_dependentSystems.foreach(_ ! SetupComplete(this))
					}
				}
				case ShutdownComplete(sys) => {
					if(sys != null)
						_notShutdownSystems -= sys
					
					if(_notShutdownSystems.isEmpty){
						shutdown()
						_shutdown = true;
						_requiredSystems.foreach(_ ! ShutdownComplete(this))
						exit()
					}
				}
				case UpdateComplete(dt,sys) => {
					if(sys != null)
						_notCompletedSystems -= sys
						
					if(_notCompletedSystems.isEmpty){
						update(dt)
						_completed = true
						_dependentSystems.foreach{_ ! UpdateComplete(dt,this)}
					}
				}
			}
		}
	}
	
	/**
	 * Extending classes must implement this method and should execute any logic that
	 * requires the use of dependent systems
	 * @param dt The change in time in seconds since the last time the update for this 
	 * System occurred
	 */
	def update(dt:Double):Unit;
	
	/**
	 * Extending classes can override this method and should execute any initialization
	 * logic that requires the use of dependent systems.
	 * Guaranteed to execute only once, and not until the setup of all required
	 * systems
	 */
	def setup():Unit = {}
	
	/**
	 * Extending classes can override this method and should execute any deinitialization
	 * logic that is required before the game shuts down.
	 * Guaranteed to execute only once. 
	 * Note that this method will execute _before_ any of its "required" Systems are deinitialized. This
	 * is the opposite order of the setup() and update() methods, and is done this way in case the current
	 * system needs to access its required Systems before shutting down.
	 */
	def shutdown():Unit = {}

	/**
	 * @return true if the System has finished updating for the current loop
	 */
	final def completed = _completed;
	
	/**
	 * Add any System references that this System requires in order to update.
	 * @param sys
	 */
	def addRequiredSystem(sys:System):Unit = {
		_requiredSystems += sys
		sys._dependentSystems += this
	}
	
	private[system] def resetLoop(){
		_notCompletedSystems = Set(_requiredSystems.toArray:_*)
		_completed = false
	}
	
	final def requiredSystems = _requiredSystems
	final def dependentSystems = _dependentSystems
}
